/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// win_al.cpp - Initializes the AL driver
//


#include "../sound/s_local.h"
#include "win_local.h"


struct alwState_t {
	ALCdevice				*hDevice;
	ALCdevice				*hCaptureDevice;

	ALCcontext				*hALC;
};

static alwState_t			alwState;

alConfig_t					alConfig;


/*
 ==============================================================================

    EXTENSIONS

 ==============================================================================
*/


static void ALW_InitExtensions ()
{
	Com_Printf("Initializing OpenAL extensions\n");
}


/*
 ==============================================================================

    DRIVER INITIALIZATION

 ==============================================================================
*/


/*
 ==================
 ALW_InitDriver

 TODO: Auto detect hardware specifics, not generic crap.. for example:
 if the computer uses a external card, use it and if the computer uses 5.1 sound system,
 use it!
 ==================
*/
static bool ALW_InitDriver ()
{
	const char   *deviceName;
	int			 attribs[] = {ALC_MONO_SOURCES, MAX_SOUND_CHANNELS, ALC_STEREO_SOURCES, 2, 0};

	Com_Printf("Initializing OpenAL driver\n");

	// Open the device
	if (s_deviceName->value[0])
		deviceName = s_deviceName->value;
	else
	{
		deviceName = (const char *)qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);

		// The "Generic Hardware" device tends to have issues due to very
		// crappy DirectSound3D drivers, so default to the "Generic Software"
		// device. This only affects Windows XP systems.
		if (!Str_ICompare(deviceName, "Generic Hardware"))
			deviceName = "Generic Software";
	}

	Com_Printf("...opening device: ");
	if ((alwState.hDevice = qalcOpenDevice(deviceName)) == NULL)
	{
		Com_Printf("failed\n");

		// Try the software device, unless we just tried it and failed
		if (!Str_ICompare(deviceName, "Generic Software"))
			return false;

		Com_Printf("...trying software device: ");
		if ((alwState.hDevice = qalcOpenDevice("Generic Software")) == NULL)
		{
			Com_Printf("failed\n");
			return false;
		}
	}
	Com_Printf("succeeded\n");

	// Open the capture device if desired
	if (s_voiceCapture->integerValue)
	{
		if (s_captureDeviceName->value[0])
			deviceName = s_captureDeviceName->value;
		else
			deviceName = (const char *)qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);

		Com_Printf("...opening capture device: ");
		if ((alwState.hCaptureDevice = qalcCaptureOpenDevice(deviceName, VOICE_RATE, AL_FORMAT_MONO16, MAX_VOICE_SAMPLES)) == NULL)
			Com_Printf("failed\n");
		else
			Com_Printf("succeeded\n");
	}

	// Create the AL context and make it current
	Com_Printf("...creating AL context: ");
	if ((alwState.hALC = qalcCreateContext(alwState.hDevice, attribs)) == NULL)
	{
		Com_Printf("failed\n");

		if (alwState.hCaptureDevice)
		{
			qalcCaptureCloseDevice(alwState.hCaptureDevice);
			alwState.hCaptureDevice = NULL;
		}

		qalcCloseDevice(alwState.hDevice);
		alwState.hDevice = NULL;

		return false;
	}
	Com_Printf("succeeded\n");

	Com_Printf("...making context current: ");
	if (!qalcMakeContextCurrent(alwState.hALC))
	{
		Com_Printf("failed\n");

		qalcDestroyContext(alwState.hALC);
		alwState.hALC = NULL;

		if (alwState.hCaptureDevice)
		{
			qalcCaptureCloseDevice(alwState.hCaptureDevice);
			alwState.hCaptureDevice = NULL;
		}

		qalcCloseDevice(alwState.hDevice);
		alwState.hDevice = NULL;

		return false;
	}
	Com_Printf("succeeded\n");

	return true;
}

/*
 ==================
 ALW_CheckVersion
 ==================
*/
static bool ALW_CheckVersion (int major, int minor)
{
	int		majorVersion, minorVersion;

	qalcGetIntegerv(alwState.hDevice, ALC_MAJOR_VERSION, 1, &majorVersion);
	qalcGetIntegerv(alwState.hDevice, ALC_MINOR_VERSION, 1, &minorVersion);

	if (majorVersion < major || (majorVersion == major && minorVersion < minor))
		return false;

	return true;
}

/*
 ==================
 ALW_StartOpenAL
 ==================
*/
static void ALW_StartOpenAL ()
{
	const char   *driver;

	// Initialize our QAL dynamic bindings
	if (s_alDriver->value[0])
		driver = s_alDriver->value;
	else
		driver = AL_DRIVER_OPENAL;

	if (!QAL_Init(driver))
		Com_Error(ERR_FATAL, "ALW_Init: could not load OpenAL subsystem");

	// Get sound and microfone capture device lists
	if (qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"))
	{
		alConfig.deviceList = (const char *)qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
		alConfig.captureDeviceList = (const char *)qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
	}
	else
	{
		alConfig.deviceList = "Generic Software\0";
		alConfig.captureDeviceList = "\0";
	}

	// Initialize the device, context, etc...
	if (!ALW_InitDriver())
	{
		QAL_Shutdown();

		Com_Error(ERR_FATAL, "ALW_Init: could not load OpenAL subsystem");
	}

	// Check for OpenAL 1.1 support
	if (!ALW_CheckVersion(1, 1))
	{
		ALW_Shutdown();

		Com_Error(ERR_FATAL, "The current sound card / driver combination does not support OpenAL 1.1");
	}

	// Get AL strings
	alConfig.vendorString = qalGetString(AL_VENDOR);
	alConfig.rendererString = qalGetString(AL_RENDERER);
	alConfig.versionString = qalGetString(AL_VERSION);
	alConfig.extensionsString = qalGetString(AL_EXTENSIONS);

	// Get ALC strings
	alConfig.alcExtensionsString = (const char *)qalcGetString(alwState.hDevice, ALC_EXTENSIONS);

	// Get device names
	alConfig.deviceName = (const char *)qalcGetString(alwState.hDevice, ALC_DEVICE_SPECIFIER);

	if (alwState.hCaptureDevice)
		alConfig.captureDeviceName = (const char *)qalcGetString(alwState.hCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER);
	else
		alConfig.captureDeviceName = NULL;

	// Get AL implementation details
	qalcGetIntegerv(alwState.hDevice, ALC_FREQUENCY, 1, &alConfig.mixerFrequency);
	qalcGetIntegerv(alwState.hDevice, ALC_REFRESH, 1, &alConfig.mixerRefresh);
	qalcGetIntegerv(alwState.hDevice, ALC_SYNC, 1, (int *)&alConfig.mixerSync);
	qalcGetIntegerv(alwState.hDevice, ALC_MONO_SOURCES, 1, &alConfig.monoSources);
	qalcGetIntegerv(alwState.hDevice, ALC_STEREO_SOURCES, 1, &alConfig.stereoSources);

	// Enable logging if desired
	QAL_EnableLogging(s_logFile->integerValue);

	alConfig.initialized = true;
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 ALW_Init
 ==================
*/
void ALW_Init ()
{
	Com_Printf("Initializing OpenAL subsystem\n");

	// Initialize OpenAL subsystem
	ALW_StartOpenAL();

	// Initialize extensions
	ALW_InitExtensions();
}

/*
 ==================
 ALW_Shutdown
 ==================
*/
void ALW_Shutdown ()
{
	Com_Printf("Shutting down OpenAL subsystem\n");

	if (alwState.hALC)
	{
		Com_Printf("...alcMakeContextCurrent( NULL ): ");
		if (!qalcMakeContextCurrent(NULL))
			Com_Printf("failed\n");
		else
			Com_Printf("succeeded\n");

		Com_Printf("...destroying AL context\n");
		qalcDestroyContext(alwState.hALC);

		alwState.hALC = NULL;
	}

	if (alwState.hCaptureDevice)
	{
		Com_Printf("...closing capture device: ");
		if (!qalcCaptureCloseDevice(alwState.hCaptureDevice))
			Com_Printf("failed\n");
		else
			Com_Printf("succeeded\n");

		alwState.hCaptureDevice = NULL;
	}

	if (alwState.hDevice)
	{
		Com_Printf("...closing device: ");
		if (!qalcCloseDevice(alwState.hDevice))
			Com_Printf("failed\n");
		else
			Com_Printf("succeeded\n");

		alwState.hDevice = NULL;
	}

	QAL_Shutdown();

	memset(&alConfig, 0, sizeof(alConfig_t));
	memset(&alwState, 0, sizeof(alwState_t));
}